package com.xyzwps.lib.ap;

import java.util.*;
import java.util.regex.Pattern;

import static com.xyzwps.lib.dollar.Dollar.*;

// TODO: 这里可改的地方还不少
// TODO: 比如，下面 todo 中列出的，再比如想办法和 bean 模块合并
public record Accessors(List<Accessor> accessors) {

    public boolean isEmpty() {
        return accessors == null || accessors.isEmpty();
    }

    public boolean isNotEmpty() {
        return !accessors.isEmpty();
    }

    public static Accessors fromTypeInfo(TypeInfo typeInfo) {
        return switch (typeInfo.kind()) {
            case CLASS -> fromClass(typeInfo);
            case RECORD -> fromRecord(typeInfo);
            case INTERFACE -> fromInterface(typeInfo);
        };
    }

    private static final Pattern SETTTER_PATTERN = Pattern.compile("^set[A-Z][a-zA-Z0-9]*$");
    private static final Pattern GETTER_PATTERN = Pattern.compile("^get[A-Z][a-zA-Z0-9]*$");
    private static final Pattern IS_PATTERN = Pattern.compile("^is[A-Z][a-zA-Z0-9]*$");

    private static final Pattern FIELD_PATTERN = Pattern.compile("^[a-z][a-zA-Z0-9]*$");

    private static Accessors fromClass(TypeInfo typeInfo) {
        var candidateFields = typeInfo.fields().stream()
                .filter(it -> !it.modifiers().isStatic())
                .filter(it -> FIELD_PATTERN.matcher(it.name()).matches())
                .toList();

        var candidateMethods = typeInfo.methods().stream()
                .filter(it -> !it.modifiers().isStatic())
                .filter(it -> it.modifiers().accessLevel() == Modifiers.AccessLevel.PUBLIC)
                .toList();

        var setterCandidates = candidateMethods.stream()
                .filter(it -> SETTTER_PATTERN.matcher(it.name()).matches())
                .filter(it -> it.element().getParameters().size() == 1) // one argument
                .toList();

        var getterCandidates = candidateMethods.stream()
                .filter(it -> GETTER_PATTERN.matcher(it.name()).matches())
                .filter(it -> it.element().getParameters().isEmpty()) // no arguments
                .toList();

        var isCandidates = candidateMethods.stream()
                .filter(it -> IS_PATTERN.matcher(it.name()).matches())
                .filter(it -> it.element().getParameters().isEmpty()) // no arguments
                .filter(it -> it.element().getReturnType().toString().equals("boolean")) // TODO: 检查 boolean
                .toList();

        var candidateNames = new TreeSet<String>();
        candidateFields.forEach(it -> candidateNames.add(it.name()));
        setterCandidates.forEach(it -> candidateNames.add(accessorName(it, 3)));
        getterCandidates.forEach(it -> candidateNames.add(accessorName(it, 3)));
        isCandidates.forEach(it -> candidateNames.add(accessorName(it, 2)));


        var nameToField = $.keyBy(candidateFields, FieldInfo::name);
        var nameToGetter = $.keyBy(getterCandidates, it -> accessorName(it, 3));
        var nameToSetter = $.keyBy(setterCandidates, it -> accessorName(it, 3));
        var nameToIs = $.keyBy(isCandidates, it -> accessorName(it, 2));

        List<Accessor> accessors = new ArrayList<>();

        for (var name : candidateNames) {
            var field = nameToField.get(name);
            var getter = nameToGetter.get(name);
            var setter = nameToSetter.get(name);
            var is = nameToIs.get(name);

            // TODO: 类型检查

            if (getter == null && setter == null && is == null) continue;

            accessors.add(new Accessor(name, field, $.defaultTo(getter, is), setter, null));
        }
        return new Accessors(accessors);
    }

    // TODO: 支持继承

    private static String accessorName(MethodInfo m, int skip) {
        var name = m.name();
        return Character.toLowerCase(name.charAt(skip)) + name.substring(skip + 1);
    }

    private static Accessors fromInterface(TypeInfo typeInfo) {

        var propertyNames = new HashSet<String>();
        var nameToGet = new HashMap<String, MethodInfo>();
        var nameToSet = new HashMap<String, MethodInfo>();
        var nameToIs = new HashMap<String, MethodInfo>();

        typeInfo.methods().stream()
                .filter(it -> !it.modifiers().isStatic()) // exclude static methods
                .map(AccessorMethod::transform)
                .forEach(it -> {
                    switch (it) {
                        case AccessorMethod.Is(String propertyName, MethodInfo m) -> {
                            propertyNames.add(propertyName);
                            nameToIs.put(propertyName, m);
                        }
                        case AccessorMethod.Get(String propertyName, MethodInfo m) -> {
                            propertyNames.add(propertyName);
                            nameToGet.put(propertyName, m);
                        }
                        case AccessorMethod.Set(String propertyName, MethodInfo m) -> {
                            propertyNames.add(propertyName);
                            nameToSet.put(propertyName, m);
                        }
                        case AccessorMethod.Nothing _ -> {
                            // do nothing
                        }
                    }
                });

        List<Accessor> accessors = new ArrayList<>();

        for (var name : propertyNames) {
            var getter = nameToGet.get(name);
            var setter = nameToSet.get(name);
            var is = nameToIs.get(name);

            // TODO: 类型检查

            if (getter == null && setter == null && is == null) continue;

            accessors.add(new Accessor(name, null, $.defaultTo(getter, is), setter, null));
        }
        return new Accessors(accessors);
    }

    private static Accessors fromRecord(TypeInfo typeInfo) {
        List<Accessor> accessors = new ArrayList<>();
        var rc = typeInfo.typeElement().getRecordComponents();
        for (var it : rc) {
            var name = it.getSimpleName().toString();
            var accessor = new Accessor(name, null, MethodInfo.of(it.getAccessor()), null, it);
            accessors.add(accessor);
        }

        return new Accessors(accessors); // TODO:
    }


    private sealed interface AccessorMethod {
        record Get(String propertyName, MethodInfo method) implements AccessorMethod {
        }

        record Set(String propertyName, MethodInfo method) implements AccessorMethod {
        }

        record Is(String propertyName, MethodInfo method) implements AccessorMethod {
        }

        enum Nothing implements AccessorMethod {
            INSTANCE
        }

        Pattern ACCESSOR_NAME_PATTERN = Pattern.compile("^(?<type>(get)|(set)|(is))(?<name>[A-Z][a-zA-Z0-9]*)$");

        static AccessorMethod transform(MethodInfo method) {
            var e = method.element();
            var matcher = ACCESSOR_NAME_PATTERN.matcher(method.name());
            if (matcher.matches()) {
                switch (matcher.group("type")) {
                    case "get" -> {
                        if (e.getParameters().isEmpty() && !e.getReturnType().toString().equals("void")) {
                            return new Get(toProperty(matcher.group("name")), method);
                        }
                    }
                    case "set" -> {
                        if (e.getParameters().size() == 1) {
                            return new Set(toProperty(matcher.group("name")), method);
                        }
                    }
                    case "is" -> {
                        if (e.getParameters().isEmpty() && e.getReturnType().toString().equals("boolean")) {
                            return new Is(toProperty(matcher.group("name")), method);
                        }
                    }
                }
            }

            return AccessorMethod.Nothing.INSTANCE;
        }

        static String toProperty(String text) {
            if (text.length() == 1) {
                return text.toLowerCase();
            }
            return Character.toLowerCase(text.charAt(0)) + text.substring(1);
        }
    }
}
